﻿/*******************************************************************************
Copyright (c) 2012-2013, PKStudio

http://pkstudio.org

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*******************************************************************************/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;
using System.Drawing.Imaging;
using System.Threading;

namespace TextureUnpacker
{
	/// <summary>
	/// MainForm of TextureUnpacker.
	/// </summary>
	public partial class MainForm : Form
	{
		private enum FileType
		{
			PngFile,
			PlistFile
		}

		string fileDir;
		string fileName;

		public MainForm()
		{
			InitializeComponent();
		}

		void MainFormDragEnter(object sender, DragEventArgs e)
		{
			if (e.Data.GetDataPresent(DataFormats.FileDrop))
				e.Effect = DragDropEffects.Link;
			else
				e.Effect = DragDropEffects.None;
		}

		void MainFormDragDrop(object sender, DragEventArgs e)
		{
			if (!e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				return;
			}

			string filePath = ((string[])e.Data.GetData(DataFormats.FileDrop))[0];
			if (Path.GetExtension(filePath).ToLower() != ".plist")
			{
				return;
			}

			// get the directory and file name.
			fileDir = Path.GetDirectoryName(filePath);
			fileName = Path.GetFileNameWithoutExtension(filePath);

			// check if the files are missing.
			if (!File.Exists(GetFilePath(FileType.PngFile, false)) || !File.Exists(GetFilePath(FileType.PlistFile, false)))
			{
				MessageBox.Show("Cannot find specified files.");
				return;
			}

			try
			{
				// make a copy for original image file.
				File.Copy(GetFilePath(FileType.PngFile, false), GetFilePath(FileType.PngFile, true), true);

				// make a copy for original plist file.
				using (StreamReader reader = new StreamReader(GetFilePath(FileType.PlistFile, false)))
				{
					using (StreamWriter writer = new StreamWriter(GetFilePath(FileType.PlistFile, true)))
					{
						string text = reader.ReadToEnd();
						text = text.Replace("\r", "").Replace("\n", "\r\n");
						writer.Write(text);
					}
				}

				// unpack the images.
				bool hasSkiped = false;
				using (Bitmap bitmap = new Bitmap(GetFilePath(FileType.PngFile, true)))
				{
					XmlDocument document = new XmlDocument();
					document.Load(GetFilePath(FileType.PlistFile, true));

					XmlNode root = document.LastChild.LastChild;
					for (int i = 0; i < root.ChildNodes.Count; i += 2)
					{
						XmlNode element = root.ChildNodes[i];
						if (element.Name == "key" && element.InnerText == "frames")
						{
							root = root.ChildNodes[i + 1];
							break;
						}
					}
					for (int i = 0; i < root.ChildNodes.Count; i += 2)
					{
						progressBar1.Value = i * 100 / root.ChildNodes.Count;
						Thread.Sleep(1);

						XmlNode element = root.ChildNodes[i];
						if (element.Name == "key" && element.InnerText.Contains(".png"))
						{
							string pngName = element.InnerText;

							XmlNode frameRoot = root.ChildNodes[i + 1];
							string frameStr = string.Empty;
							string offsetStr = string.Empty;
							string rotatedStr = string.Empty;
							string sourceSizeStr = string.Empty;
							for (int j = 0; j < frameRoot.ChildNodes.Count; j += 2)
							{
								element = frameRoot.ChildNodes[j];
								if (element.Name == "key")
								{
									if (element.InnerText == "frame")
									{
										frameStr = frameRoot.ChildNodes[j + 1].InnerText;
									}
									else if (element.InnerText == "offset")
									{
										offsetStr = frameRoot.ChildNodes[j + 1].InnerText;
									}
									else if (element.InnerText == "rotated")
									{
										rotatedStr = frameRoot.ChildNodes[j + 1].Name;
									}
									else if (element.InnerText == "sourceSize")
									{
										sourceSizeStr = frameRoot.ChildNodes[j + 1].InnerText;
									}
								}
							}

							Rectangle frame = StringToRectangle(frameStr);
							if (frame.IsEmpty)
							{
								hasSkiped = true;
								continue;
							}
							Point offset = StringToPoint(offsetStr);
							bool rotated = StringToBool(rotatedStr);
							Size sourceSize = StringToSize(sourceSizeStr);
							if (sourceSize.IsEmpty)
							{
								hasSkiped = true;
								continue;
							}

							using (Bitmap pngFile = new Bitmap(sourceSize.Width, sourceSize.Height, PixelFormat.Format32bppArgb))
							{
								int startX = sourceSize.Width / 2 + offset.X - frame.Width / 2;
								int startY = sourceSize.Height / 2 - offset.Y - frame.Height / 2;
								if (rotated)
								{
									for (int y = 0; y < frame.Height; y++)
									{
										for (int x = 0; x < frame.Width; x++)
										{
											pngFile.SetPixel(x + startX, y + startY, bitmap.GetPixel(frame.Left + frame.Height - y, frame.Top + x));
										}
									}
								}
								else
								{
									for (int y = 0; y < frame.Height; y++)
									{
										for (int x = 0; x < frame.Width; x++)
										{
											pngFile.SetPixel(x + startX, y + startY, bitmap.GetPixel(frame.Left + x, frame.Top + y));
										}
									}
								}
								pngFile.Save(Path.Combine(this.fileDir, pngName), ImageFormat.Png);
							}
						}
					}
				}

				// delete the copied plist file.
				File.Delete(GetFilePath(FileType.PlistFile, true));

				progressBar1.Value = 100;
				if (hasSkiped)
				{
					MessageBox.Show(GetFilePath(FileType.PlistFile, false) + "Unpack frames successfully.\nSome frames are not unpacked due to wrong properties.");
				}
				else
				{
					MessageBox.Show(GetFilePath(FileType.PlistFile, false) + "Unpack frames successfully.");
				}
			}
			catch (Exception exception)
			{
				MessageBox.Show(exception.Message, "Error");
			}
		}

		static Rectangle StringToRectangle(string param)
		{
			Match match = Regex.Match(param, @"\{\{(?<x>\d+),(?<y>\d+)\},\{(?<w>\d+),(?<h>\d+)\}\}");
			if (match != null)
			{
				int left = int.Parse(match.Groups["x"].Value);
				int top = int.Parse(match.Groups["y"].Value);
				int width = int.Parse(match.Groups["w"].Value);
				int height = int.Parse(match.Groups["h"].Value);
				return new Rectangle(left, top, width, height);
			}
			return Rectangle.Empty;
		}

		static Point StringToPoint(string param)
		{
			Match match = Regex.Match(param, @"\{(?<x>-?\d+),(?<y>-?\d+)\}");
			if (match != null)
			{
				int left = int.Parse(match.Groups["x"].Value);
				int top = int.Parse(match.Groups["y"].Value);
				return new Point(left, top);
			}
			return Point.Empty;
		}

		static Size StringToSize(string param)
		{
			Match match = Regex.Match(param, @"\{(?<w>\d+),(?<h>\d+)\}");
			if (match != null)
			{
				int width = int.Parse(match.Groups["w"].Value);
				int height = int.Parse(match.Groups["h"].Value);
				return new Size(width, height);
			}
			return Size.Empty;
		}

		static bool StringToBool(string param)
		{
			return param.Equals("true", StringComparison.OrdinalIgnoreCase);
		}

		string GetFilePath(FileType fileType, bool isBackup)
		{
			switch (fileType)
			{
				case FileType.PngFile:
					if (isBackup)
					{
						return Path.Combine(this.fileDir, this.fileName + "_bak.png");
					}
					return Path.Combine(this.fileDir, this.fileName + ".png");
				case FileType.PlistFile:
					if (isBackup)
					{
						return Path.Combine(this.fileDir, this.fileName + "_bak.plist");
					}
					return Path.Combine(this.fileDir, this.fileName + ".plist");
			}
			return null;
		}
	}
}
